var events = require('events');
var util = require('util');
var xlogger = require('./xlogger');
var appdef = require('./def/appdef.js').defines;
var config = require('./configmanager');
var eventManager = require('./eventmanager');
var fsmManager = require('./fsmmanager');
var ssnManager = require('./sessionmanager');

var AppEvent = require('./common/appevent.js');
var hashTable = require('./common/hashtable.js');
var appUtil = require('./common/apputility.js');
//var stackManager = require('./stackmanager');
//var subsManager = require('./subsmanager');

var MODNAME = 'Kernel';
var CONFIG_FILENAME = '';

var Core = function() {
	events.EventEmitter.call(this);
	
	this.XLOGGER = null;
	this.XAPPCONFIG = null;
	this.XEVENTMANAGER = null;
	this.XSSNMANAGER = null;
	this._StackList = hashTable.create();
	//this.XSTACKMANAGER = null;
	//this.XSUBSMANAGER = null;
	
	var logger = xlogger.create({'TRACELEVEL': appdef.DEF_TRACE_LEVEL, 'MODE': appdef.DEF_TRACE_MODE});
	this.XLOGGER = logger;
};
util.inherits(Core, events.EventEmitter);

Core.prototype.init = function (pConfFileName) {
	CONFIG_FILENAME = pConfFileName;
	// Load Config file
	this.loadConfig();
	
	// Load EventManager
	this.loadEventManager();
	
	// load sessionmanager
	this.loadSsnManager();
	
	// load stack modules
	this.loadModules();
	
	//display eventlist
	this.XEVENTMANAGER.printEventsList();
	
};

Core.prototype.exit = function () {
	//TODO: clear all resources
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_1', 'unloading modules...');
	
	// close eventmanager
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'closing EventManager...');
	if (this.XEVENTMANAGER) this.XEVENTMANAGER.close();
	// close sessionmanager
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'closing SessionManager...');
	if (this.XSSNMANAGER) this.XSSNMANAGER.close();


	//close modules
	//call onExit event of all modules
	for (var i = 0, keys = this._StackList.keys(), len = keys.length; i < len; i++) {
		var handle = this._StackList.getItem(keys[i]);
		handle.onExit();
	}
	// clear the stacklist
	this._StackList.clear();
	
	// close logger
	if (this.XLOGGER) this.XLOGGER.close();
	
};

Core.prototype.loadConfig = function () {
	
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', this.XLOGGER.getTraceOptions());
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loading Configuration Settings...');
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_3', 'Config FileName: ' + CONFIG_FILENAME);
	
	var confJsonObj = config.load(CONFIG_FILENAME);
	this.XAPPCONFIG = confJsonObj;

	if (this.XAPPCONFIG.CORE.TRACING) {
		this.XLOGGER.loadTraceOptions(this.XAPPCONFIG.CORE.TRACING);
	}
	
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', this.XLOGGER.getTraceOptions());
};

Core.prototype.loadEventManager = function () {

	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Initializing EventManager...');
	this.XEVENTMANAGER = eventManager.create(this);
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loaded EventManager successfully');
};
Core.prototype.loadSsnManager = function () {

	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Initializing Core-SessionManager...');
	this.XSSNMANAGER = ssnManager.create(this);
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loaded Core-SessionManager successfully');
};
Core.prototype.loadModules = function (pObj) {
	var self = this;
	
	//var log = this.XLOGGER.log;
	self.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loading Modules...');
	
	// Load Subscribers
	var propName = "MODULES";
	if(!self.XAPPCONFIG.hasOwnProperty(propName)) {
		throw "Config section ["+propName+"] not found";
	}
	
	//self.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loading Stack Modules...');
	var stackList = this.XAPPCONFIG["MODULES"];	
	for(var i=0; i<stackList.length; i++) {
		var modConfig = stackList[i];
		
		propName = "NAME";
		if(!modConfig.hasOwnProperty(propName)) {
			throw "Config Property ["+propName+"] not found";
		}
		if(modConfig[propName].length < 2) {
			throw "Module name should be at least 2 chars";
		}
		var modName = modConfig[propName];
		
		propName = 'LOAD';
		if (!modConfig.hasOwnProperty(propName)) {
			throw "Config Property ["+propName+"] of module ["+ modName +"] not found";
		}
		//propName = "TYPE";
		//if(!stackObj.hasOwnProperty(propName)) throw "Config Property ["+propName+"] of module ["+ modName +"] not found";
		propName = "PARAMS";
		if(!modConfig.hasOwnProperty(propName)) {
			throw "Config Property ["+propName+"] of module ["+ modName +"] not found";
		}
		
		if ('YES' === (modConfig.LOAD=modConfig.LOAD.toUpperCase())) {
					
			var path = './stacks/'+modConfig.NAME;
			self.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loading module ['+modConfig.NAME+']');
			var modHandle = null;
			try {
				modHandle = require(path).create(self, modConfig);
			} catch (e) {
				self.XLOGGER.log(MODNAME, 'ERROR', 'Failed to create instance of the module ['+modConfig.NAME+'], Err: '+e.stack);
				continue;
			}
			self.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Created module instance ['+modConfig.NAME+']');
			self.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Initializing module['+modConfig.NAME+'], ConfigParams= '+ JSON.stringify(modConfig.PARAMS));
			
			var status = self.initializeModule(modHandle, modConfig);
			if (status) {
				this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Status: module ['+modConfig.NAME+'] is initialized successfully');
			} else {
				this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Status: Failed to initialize module ['+modConfig.NAME+']');
			}
		}
		
	}
	
};

Core.prototype.loadSubscriber = function (createSubscriber) {
	var self = this;
	
	// create subsciber
	var modObj = createSubscriber();
	//console.log(subsObj);
	if(!modObj.name){
		throw new Error("Subscriber name not defined");
	}
	
	var modName = modObj.name;
	if(modName.length < 2) {
		throw "Module name should be at least 2 chars";
	}
	// Get subscirber config object
	var modConf = this.getSubscriberConfig(modName);
	if(!modConf) {
		modConf = {'NAME': modName, 'PARAMS':{}};
	}
	
	self.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loading Subscriber ['+modName+']...');
	self.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'ConfigParams: '+ JSON.stringify(modConf));
	
	var status = self.initializeModule(modObj, modConf);
		
	if(status) {
		this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Status: module ['+modName+'] is initialized successfully');
	} else {
		this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Status: Failed to initialize module ['+modName+']');
	}
	
};


Core.prototype.initializeModule = function (pModObj, pModConf) {
	var status = false;
	var self = this;

	var handle = pModObj;
	
	
	// call module init
	if(typeof handle.onInit !== 'function') {
		self.XLOGGER.log(MODNAME, 'ERROR', 'method [onInit] not found for the module ['+pModConf.NAME+']');
		return status;		
	} else {
		// initialize handle obj
		handle._Config = pModConf;
		handle._Manager = self;
		handle.logger = self.XLOGGER;
		handle._FsmManager = fsmManager.create(handle);
		
		// create functions
		handle.sendEvent = function (pEventObj) {
			var self = this;
			
			var eventName = pEventObj.getEventName();
			//var evt = self._Config.NAME+'_'+eventName;
			setTimeout(function() {
				self._Manager.XLOGGER.log(MODNAME, 'INFO_LEVEL_2','Sending ['+eventName+'] to EventManager');
				self._Manager.XEVENTMANAGER.dispatchEvent(self, pEventObj);
				
			}, 1);
		};
		
		handle.createNewEvent = function(pEventName, pEventType, pPayload, pRespEventName) {
						
			var eventObj = new AppEvent(pEventName, pEventType);
			if (pPayload) {
				eventObj.setPayload(pPayload);
			}
			if (pRespEventName) {
				eventObj.setRespEventName(pRespEventName);
			}
			return eventObj;
		};
		
		handle.createRespEvent = function (pPayload, pRcvdEvent) {
			var self = this;
			var payload = pPayload || {};
			var rcvdEvent = pRcvdEvent;
						
			var eventName = rcvdEvent.getRespEventName();
			if (!eventName) {
				self._Manager.XLOGGER.log(MODNAME, 'WARNING', 'createRespEvent: invalid eventname');
			}
			
			var eventObj = this.createNewEvent(eventName, 'RESPONSE');
			eventObj.setEventKey(rcvdEvent.getEventKey());
			if (rcvdEvent.reqFsmSessionId) {
				eventObj.setEventKey(rcvdEvent.reqFsmSessionId);
			}
			eventObj.setPayload(payload);
			
			return eventObj;
		};
		
		handle.subscribeEvent = function(pEventName, pEventHandler, pAllowMultipleSubscriptions) {
			var self = this;
			return self._Manager.subscribeEvent(this, pEventName, pEventHandler, pAllowMultipleSubscriptions);
		};
		// call init
		self.XLOGGER.log(MODNAME, 'INFO_LEVEL_3', 'invoked '+pModConf.NAME+'.onInit');
		status =handle.onInit(self);
		if (!status) {
			self.XLOGGER.log(MODNAME, 'ERROR', 'Failed to process onInit method for the module ['+pModConf.NAME+']');
			return status;
		}
		
	}
	// check onExit func
	if(typeof handle.onExit !== 'function') {
		self.XLOGGER.log(MODNAME, 'ERROR', 'method [onExit] not found for the stack ['+pModConf.NAME+']');
		return status;
	}
	
	// Register the subscriptions	
	
	if(typeof handle.onSubscribe !== 'function') {
		self.XLOGGER.log(MODNAME, 'ERROR', 'method [onSubscribe] not found for the stack ['+pModConf.NAME+']');
		return status;
	} else {
		self.XLOGGER.log(MODNAME, 'INFO_LEVEL_3', 'invoked '+pModConf.NAME+'.onSubscribe');
		status = handle.onSubscribe(self);
		if (!status) {
			self.XLOGGER.log(MODNAME, 'ERROR', 'Failed to process onSubscribe method for the module ['+pModConf.NAME+']');
			return status;
		}
	}
	/*
	//subscriber for stackevents	
	if (handle._Events) {
		var stackEvents = handle._Events;
		for(var i=0; i<stackEvents.length; i++) {
			var evt = handle._Config.NAME+'_'+stackEvents[i];
			handle.on(evt, function(pStackObj, pEventObj) {
				var eventName = pEventObj.getEventName();
				self.XLOGGER.log(MODNAME, 'INFO_LEVEL_3','Sending ['+eventName+'] to EventManager');
				self.XEVENTMANAGER.dispatchEvent(self, pEventObj);
			});
			self.XLOGGER.log(MODNAME, 'INFO_LEVEL_3', 'subscribed to listen ['+evt+'] with the stack ['+handle._Config.NAME+']');
		}
	}
	*/
	
	//register stack onEvent listener	
	if(typeof handle.onEvent !== 'function') {
		self.XLOGGER.log(MODNAME, 'ERROR', 'method [onEvent] not found for the module ['+pModConf.NAME+']');
		return status;
	}
	//self.on(pParamsObj.NAME, (handle.onEvent).bind(handle));
	
	//Store the handle in the hashtable
	var stackObj = {
			'NAME':	pModConf.NAME,
			'HANDLE': handle
	};
	self._StackList.setItem(pModConf.NAME, stackObj);

	
	status = true;
	return status;
};


Core.prototype.subscribeEvent = function (pModObj, pEventName, pEventHandler, pAllowMultiSubscrptions) {
	// Load stack Config Object
	var modConf = pModObj._Config;
	if(pAllowMultiSubscrptions === undefined) pAllowMultiSubscrptions = 1;
	
	var eventName = pEventName;
	if(eventName === '*'){
		eventName = modConf.NAME;
		pAllowMultiSubscrptions = 0;
	}
	if (pEventHandler === null) {
		pEventHandler = pModObj.onEvent;
	}
	// call eventmanager
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_3', 'Registering the event ['+pEventName+']');

	var status = this.XEVENTMANAGER.add(pModObj, pEventName, pEventHandler, pAllowMultiSubscrptions);
		
	if (status === true)
		this.XLOGGER.log(MODNAME, 'INFO_LEVEL_3', 'Registered successfully, Event: [ NAME:'+ pEventName +'] for module [NAME: '+ modConf.NAME +', MULTISUBSCRIPTIONS: '+ pAllowMultiSubscrptions +']');
	else
		this.XLOGGER.log(MODNAME, 'WARNING', 'Failed to register Event: [ NAME:'+ pEventName +'] for module [NAME: '+ modConf.NAME +']');
	
	
	return true;
};


Core.prototype.getSubscriberConfig = function (pSubsName) {
	if (!this.XAPPCONFIG["SUBSCRIBERS"])
		throw new Error("Missing [SUBSCRIBERS] section in config file");
	
	var subsList = this.XAPPCONFIG["SUBSCRIBERS"];
	var subsConfig = null;
	for(var i=0; i<subsList.length; i++) {
		var subsObj = subsList[i];
		var propName = 'PARAMS';
		if (!subsObj.hasOwnProperty(propName)) throw "Subscribers['+pSubsName+'] Config Property ["+propName+"] not found";
		propName = 'NAME';
		if (!subsObj.hasOwnProperty(propName)) throw "Subscribers['+pSubsName+'] Config Property ["+propName+"] not found";
		
		if (subsObj['NAME'] === pSubsName) {
			subsConfig = subsObj;
			break;
		}
	}
	
	return subsConfig;
};

// currently this method is not in use
Core.prototype.loadSubscribers = function (pObj) {
	//var log = this.XLOGGER.log;
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loading Subscribers...');
	
	var propName = "SUBSCRIBERS";
	if(!this.XAPPCONFIG.hasOwnProperty(propName)) throw "Config Property ["+propName+"] not found";

	// Create SubsManager Object
	this.XSUBSMANAGER = subsManager.create(this);
	
	// Load Subscribers
	var subsList = this.XAPPCONFIG["SUBSCRIBERS"];
	
	for(var i=0; i<subsList.length; i++) {
		var subsObj = subsList[i];
		var subsStatus = 0;
		propName = 'NAME';
		if (!subsObj.hasOwnProperty(propName)) throw "Config Property ["+propName+"] not found";
		propName = 'LOAD';
		if (!subsObj.hasOwnProperty(propName)) throw "Config Property ["+propName+"] not found";
		propName = 'PARAMS';
		if (!subsObj.hasOwnProperty(propName)) throw "Config Property ["+propName+"] not found";
		
		if ('YES' === (subsObj.LOAD=subsObj.LOAD.toUpperCase())) {
			this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Initializing ['+subsObj.NAME+'], ConfigParams= '+ JSON.stringify(subsObj.PARAMS));
			
			subsStatus = this.XSUBSMANAGER.add(subsObj);
			if(subsStatus)
				this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Status: Loaded '+subsObj.NAME+' successfully');
			else
				this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Status: Failed to load '+subsObj.NAME);
		}
	}
	this.XLOGGER.log(MODNAME, 'INFO_LEVEL_2', 'Loaded all Subscribers Successfully...');
};

Core.prototype.printEventsList = function (pObj) {
	
};

module.exports.create = function() {
	return new Core();
};


	